/*
 Copyright (c) 2013- MeherTJ G.
 All rights reserved. Everybody can use these code freely.
 */

#include "zen_csv.h"

namespace Zen {
	static const char Quote = '"';
	static const char Comma = ',';
	static const char Endl = '\n';
	static const char Return = '\r';

	CSV::CSV()
	{
	}
	
	void CSV::clear()
	{
		m_rows.clear();
	}
	
	std::string CSV::encode() const
	{
		std::string res;
		int rn = 0;
		for(auto & r : m_rows)
		{
			if(rn)
			{
				res.push_back(Return);
				res.push_back(Endl);
			}
			++rn;

			int cn = 0;
			for(auto & c : r)
			{
				if(cn) res.push_back(Comma);
				++cn;
				
				if(c.find(Comma) != c.npos ||
				   c.find(Quote) != c.npos ||
				   c.find(Endl) != c.npos ||
				   c.find(Return) != c.npos)
				{
					res.push_back(Quote);
					for(auto i : c)
					{
						if(i == Quote)
						{
							res.push_back(Quote);
							res.push_back(Quote);
						}
						else
						{
							res.push_back(i);
						}
					}
					res.push_back(Quote);
				}
				else
				{
					res.append(c);
				}
			}
		}
		return res;
	}
	
	CSV::Rows & CSV::rows()
	{
		return m_rows;
	}
	
	CSV::Rows const & CSV::rows() const
	{
		return m_rows;
	}
	

	/// @_CSVHelp
	/// recursive. if run a large data, maybe has a stack overflow.
	/// use CSVHelpNR__ instead.
#if 0
	class _CSVHelp
	{
		std::string const & data;
		size_t & index;
		CSV::Row & row;
		std::string temp;
		bool end;
	public:
		_CSVHelp(std::string const & data_, size_t & index_, CSV::Row & row_)
		: data(data_), index(index_), row(row_)
		{
			end = false;
		}
		bool analyse()
		{
			return readCol();
		}
	private:
		bool readCol()
		{
			if(index < data.size() && data[index] == Quote)
			{
				++index;
				return readQuote();
			}
			return readNoQuote();
		}
		bool readQuote()
		{
			if(index >= data.size())
				return false;
			auto c = data[index++];
			
			if(c == Quote) return onQuoteEnd();
			temp.push_back(c);
			return readQuote();
		}
		bool onQuoteEnd()
		{
			if(index >= data.size()) return onNoMore();
			auto c = data[index++];
			
			if(c == Endl) return onEnd();
			if(c == Return) return onRt();
			if(c == Comma) return onSep();
			if(c == Quote)
			{
				temp.push_back(Quote);
				return readQuote();
			}
			else return false;
		}
		bool readNoQuote()
		{
			if(index >= data.size()) return onNoMore();
			auto c = data[index++];
			if(c == Endl) return onEnd();
			if(c == Return) return onRt();
			if(c == Comma) return onSep();
			
			temp.push_back(c);
			return readNoQuote();
		}
		bool onNoMore()
		{
			row.push_back(temp);
			return true;
		}
		bool onRt()
		{
			if(index >= data.size()) return onNoMore();
			if(data[index] == Endl)
			{
				++index;
				return onEnd();
			}
			return onNoMore();
		}
		bool onEnd()
		{
			row.push_back(temp);
			return true;
		}
		bool onSep()
		{
			row.push_back(temp);
			temp.clear();
			return readCol();
		}
	};

#else

	class CSVHelpNR__
	{
	public:
		typedef CSVHelpNR__ Me;
		std::string const & data;
		size_t & index;
		CSV::Row & row;
		std::string temp;
		bool end;
		const char * _next_name = nullptr;

		std::function<bool ()> _next;

		typedef bool (CSVHelpNR__::*func)();

		inline bool _SET_NEXT(func f, const char * name, bool ret)
		{
			_next_name = name;
			if(f) _next = std::bind(f, this);
			else _next = nullptr;
			return ret;
		}
		#define SET_NEXT_R(f, ret) _SET_NEXT(f, __func__, ret);
		#define SET_NEXT(f) _SET_NEXT(f, __func__, true)
	public:

		CSVHelpNR__(std::string const & data_, size_t & index_, CSV::Row & row_)
		: data(data_), index(index_), row(row_)
		{
			end = false;
		}
		bool analyse()
		{
			SET_NEXT(&Me::readCol);
			while(_next != nullptr)
			{
				if(!_next()) return false;
			}
			return true;
		}

	private:
		bool readCol()
		{
			if(index < data.size() && data[index] == Quote)
			{
				++index;
				return SET_NEXT(&Me::readQuote);
			}
			else
			{
				return SET_NEXT(&Me::readNoQuote);
			}
		}
		bool readQuote()
		{
			if(index >= data.size())
				return SET_NEXT_R(nullptr, false)

			auto c = data[index++];

			if(c == Quote)
			{
				return SET_NEXT(&Me::onQuoteEnd);
			}
			else {
				temp.push_back(c);
				return SET_NEXT(&Me::readQuote);
			}
		}
		bool onQuoteEnd()
		{
			if(index >= data.size())
			{
				return SET_NEXT(&Me::onNoMore);
			}
			auto c = data[index++];

			if(c == Endl) return SET_NEXT(&Me::onEnd);
			if(c == Return) return SET_NEXT(&Me::onRt);
			if(c == Comma) return SET_NEXT(&Me::onSep);
			if(c == Quote)
			{
				temp.push_back(Quote);
				return SET_NEXT(&Me::readQuote);
			}
			else
			{
				return SET_NEXT_R(nullptr, false)
			}
		}
		bool readNoQuote()
		{
			if(index >= data.size()) return SET_NEXT(&Me::onNoMore);
			auto c = data[index++];
			if(c == Endl) return SET_NEXT(&Me::onEnd);
			if(c == Return) return SET_NEXT(&Me::onRt);
			if(c == Comma) return SET_NEXT(&Me::onSep);

			temp.push_back(c);
			return SET_NEXT(&Me::readNoQuote);
		}
		bool onNoMore()
		{
			row.push_back(temp);
			return SET_NEXT(nullptr);
		}
		bool onRt()
		{
			if(index >= data.size()) return SET_NEXT(&Me::onNoMore);
			if(data[index] == Endl)
			{
				++index;
				return SET_NEXT(&Me::onEnd);
			}
			return SET_NEXT(&Me::onNoMore);
		}
		bool onEnd()
		{
			row.push_back(temp);
			return SET_NEXT(nullptr);
		}
		bool onSep()
		{
			row.push_back(temp);
			temp.clear();
			return SET_NEXT(&Me::readCol);
		}
	};
#endif
	
	bool CSV::decode(std::string const & content)
	{
		size_t index = 0;
		while (index < content.size())
		{
			CSV::Row row;
			CSVHelpNR__ run(content, index, row);
			if(!run.analyse())
			{
				return false;
			}
			m_rows.push_back(row);
		}
		return true;
	}
}
